/*
 * Copyright 2007-2009 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package net.paoding.rose.impl.scanning;

import static net.paoding.rose.RoseConstants.CONF_CONTROLLERS_IGNORED;
import static net.paoding.rose.RoseConstants.CONF_CONTROLLERS_PATH;
import static net.paoding.rose.RoseConstants.CONTROLLERS_DIRECTORY_NAME;

import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.vfs.FileName;
import org.apache.commons.vfs.FileObject;
import org.apache.commons.vfs.FileSystemException;
import org.apache.commons.vfs.FileType;

/**
 * 
 * @author zhiliang.wang [qieqie.wang@gmail.com]
 * 
 */
public class ModuleScannerImpl extends AbstractScanner implements ModuleScanner {

	private List<String> modulePaths = new ArrayList<String>();
	private Map<String, ArrayList<Class<?>>> moduleClasses = new HashMap<String, ArrayList<Class<?>>>();
	private Map<String, ArrayList<URL>> moduleContexts = new HashMap<String, ArrayList<URL>>();

	// --------------------------------------------------------------------

	@Override
	public List<String> getModulePaths() {
		return Collections.unmodifiableList(modulePaths);
	}

	@Override
	public List<Class<?>> getModuleClasses(String modulePath) {
		List<Class<?>> classes = this.moduleClasses.get(modulePath);
		if (classes == null) {
			return Collections.emptyList();
		}
		return Collections.unmodifiableList(classes);
	}

	@Override
	public List<URL> getContextResouces(String modulePath) {
		List<URL> resources = this.moduleContexts.get(modulePath);
		if (resources == null) {
			return Collections.emptyList();
		}
		return Collections.unmodifiableList(resources);
	}

	// --------------------------------------------------------------------

	@Override
	protected void clear() {
		modulePaths.clear();
		moduleClasses.clear();
		moduleContexts.clear();
		modulePathMap.clear();
	}

	@Override
	protected void afterScanning() {
		super.afterScanning();
		Iterator<String> iter = modulePaths.iterator();
		while (iter.hasNext()) {
			String modulePath = iter.next();
			if (moduleClasses.get(modulePath).size() == 0
					&& moduleContexts.get(modulePath).size() == 0) {
				iter.remove();
				if (logger.isDebugEnabled()) {
					logger.debug("remove empty module '" + modulePath + "'");
				}
			}
		}
		// dump
		if (logger.isInfoEnabled()) {
			iter = modulePaths.iterator();
			while (iter.hasNext()) {
				String modulePath = iter.next();
				ArrayList<Class<?>> classes = moduleClasses.get(modulePath);
				ArrayList<URL> contextes =  moduleContexts.get(modulePath);
				logger.info("module '" + modulePath + "' resources: classes=" + Arrays.toString(classes.toArray())
					+ "; contexts=" + Arrays.toString(contextes.toArray()));
			}
		}
	}

	@Override
	protected boolean isFolderMatched(FileObject folderObject) {
		return CONTROLLERS_DIRECTORY_NAME.equals(folderObject.getName().getBaseName());
	}

	private Map<FileObject, String> modulePathMap = new HashMap<FileObject, String>();

	@Override
	protected void handleWithFolder(FileObject rootObject, FileObject matchedRootFolder,
			FileObject thisFolder) throws IOException {
		String modulePath = "";
		if (matchedRootFolder != thisFolder) {
			modulePath = matchedRootFolder.getName().getRelativeName(thisFolder.getName());
			modulePath = "/" + modulePath;
		}

		// 如果rose.properties设置了controllers的controllers.path
		// (null if there is no such child.)
		FileObject rosePropertiesFile = thisFolder.getChild("rose.properties");
		if (rosePropertiesFile != null) {
			Properties p = new Properties();
			InputStream in = rosePropertiesFile.getContent().getInputStream();
			p.load(in);
			in.close();

			// 如果controllers=ignored，则...
			String ignored = p.getProperty(CONF_CONTROLLERS_IGNORED, "false").trim();
			if ("true".equalsIgnoreCase(ignored)) {
				if (logger.isDebugEnabled()) {
					logger.info("ignored controllers " + thisFolder.getName());
				}
				return;
			}
			// 
			modulePath = p.getProperty(CONF_CONTROLLERS_PATH, modulePath).trim();
			if (modulePath.indexOf("${parent}") != -1) {
				if (thisFolder.getParent() != null) {
					String parentPath = modulePathMap.get(thisFolder.getParent());
					if (parentPath == null) {
						parentPath = "";
					}
					modulePath = modulePath.replace("${parent}", parentPath);
				} else {
					modulePath = modulePath.replace("${parent}", "");
				}
			}
			// 空串，或，以/开头的串，不能以/结尾，不能重复/
			if (modulePath.length() != 0) {
				while (modulePath.indexOf("//") != -1) {
					modulePath = modulePath.replace("//", "/");
				}
				while (modulePath.endsWith("/")) {
					modulePath = modulePath.substring(0, modulePath.length() - 1);
				}
				if (!modulePath.startsWith("/")) {
					modulePath = "/" + modulePath;
				}
			}
		}
		modulePathMap.put(thisFolder, modulePath);
		//
		if (!modulePaths.contains(modulePath)) {
			modulePaths.add(modulePath);
			ArrayList<URL> resources = new ArrayList<URL>();
			moduleContexts.put(modulePath, resources);
			ArrayList<Class<?>> classes = new ArrayList<Class<?>>();
			moduleClasses.put(modulePath, classes);
		}
		logger.info("found module '" + modulePath + "' in " + thisFolder);
		FileObject[] children = thisFolder.getChildren();
		for (FileObject child : children) {
			if (child.getType().equals(FileType.FILE)) {
				FileObject resource = child;
				handlerModuleResource(rootObject, modulePath, resource);
			}
		}
		for (FileObject child : children) {
			if (child.getType().equals(FileType.FOLDER)) {
				handleWithFolder(rootObject, matchedRootFolder, child);
			}
		}
	}
	

	protected void handlerModuleResource(FileObject rootObject, String modulePath,
			FileObject resource) throws FileSystemException {
		FileName fileName = resource.getName();
		String bn = fileName.getBaseName();
		if (bn.endsWith(".class") && bn.indexOf('$') == -1) {
			addModuleClass(rootObject, modulePath, resource);
		} else if ((bn.startsWith("module") || bn.startsWith("applicationContext"))
				&& bn.endsWith(".xml")) {
			addModuleContext(rootObject, modulePath, resource);
		}
	}

	private void addModuleContext(FileObject rootObject, String modulePath, FileObject resource)
			throws FileSystemException {
		ArrayList<URL> resources = moduleContexts.get(modulePath);
		resources.add(resource.getURL());
		if (logger.isDebugEnabled()) {
			logger.debug("module '" + modulePath + "': found context file, url=" + resource.getURL());
		}
	}

	private void addModuleClass(FileObject rootObject, String modulePath, FileObject resource)
			throws FileSystemException {
		String className = rootObject.getName().getRelativeName(resource.getName());
		className = StringUtils.removeEnd(className, ".class");
		className = className.replace('/', '.');
		ArrayList<Class<?>> classes = moduleClasses.get(modulePath);
		for (int i = classes.size() - 1; i >= 0; i--) {
			Class<?> clazz = classes.get(i);
			if (clazz.getName().equals(className)) {
				if (logger.isDebugEnabled()) {
					logger.info("module '" + modulePath + "': skip replicated class " + className);
				}
				return;
			}
		}
		try {
			classes.add(Class.forName(className));
			if (logger.isDebugEnabled()) {
				logger.debug("module '" + modulePath + "': found class, name=" + className);
			}
		} catch (ClassNotFoundException e) {
			logger.error("", e);
		}
	}

	@Override
	protected String getDefaultJarName() {
		return super.getDefaultJarName() + ", *-controllers.jar";
	}

}
